﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Jb.Domain;
using Jb.Common.Extensions;
using Lucene.Net.Index;
using Lucene.Net.Store;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Documents;
using Lucene.Net.Analysis;

namespace Jb.Search.Indexer
{
    internal class ResumeIndexer : IIndexer
    {
        private IndexWriter _IndexWriter = null;
        private JbEntities _Context = null;

        #region Constructor

        public ResumeIndexer()
        {
            InitIndexWriter(SearchConstants.ResumeIndexDirectory);
        }

        #endregion

        #region IIndexer Members

        /// <summary>
        /// Add a Resume Object to the Lucene Index
        /// </summary>
        /// <typeparam name="T">Type of Resume</typeparam>
        /// <param name="obj"></param>
        /// <param name="appName"></param>
        public void IndexDocument<T>(T obj, string appName) where T : class
        {
            IndexDocument(obj as Resume, appName);
        }

        /// <summary>
        /// Deletes the existing index trace from Lucene and DB then Inserts index in Lucene and DB as well.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="obj"></param>
        /// <param name="appName"></param>
        public void UpdateIndex<T>(T obj, string appName) where T : class
        {
            UpdateIndex(obj as Resume, appName);
        }

        /// <summary>
        /// Deletes a Lucene Index by resume id, as we have configured lucene to think resume id as primary key.
        /// </summary>
        /// <param name="productID"></param>
        public void DeleteIndex(int resumeID)
        {
            _IndexWriter.DeleteDocuments(new Term("id", resumeID.ToString()));
            ResumeIndex resumeIndex = _Context.ResumeIndexes.FirstOrDefault(P => P.ResumeID == resumeID);
            if (resumeIndex != null)
            {
                _Context.DeleteObject(resumeIndex);
                _Context.SaveChanges();
            }
            //_IndexWriter.Commit();
        }

        public void CloseWriter()
        {
            //_IndexWriter.Close();
            _IndexWriter.Dispose();
        }

        public void CommitChanges()
        {
            _IndexWriter.Commit();
        }

        public void Optimize()
        {
            _IndexWriter.Optimize();
        }

        /// <summary>
        /// Deletes existing indexes and rebuilds index in DB and Lucene.
        /// Selects only those Resumes who has following criteria:
        /// 1. JobSeeker Is Active
        /// 2. Resume Is Active
        /// 3. Job Seeker has completed their profile
        /// </summary>
        /// <returns></returns>
        public ReIndexResult ReIndex(int processStatusJobID)
        {
            ReIndexResult result = new ReIndexResult();
            Domain.ProcessStatus processStatus = _Context.ProcessStatuses.FirstOrDefault(P => P.ID == processStatusJobID);
            ///Delete All ProductIndexes from DB and Lucene Index
            foreach (ResumeIndex resumeIndex in _Context.ResumeIndexes)
            {
                _IndexWriter.DeleteDocuments(new Term("id", resumeIndex.ResumeID.ToString()));
                _Context.ResumeIndexes.DeleteObject(resumeIndex);
                result.DeletedIndexFromDB = result.DeletedIndexFromDB + 1;
                result.DeletedIndexFromLucene = result.DeletedIndexFromLucene + 1;
            }
            this.CommitChanges();
            _Context.SaveChanges();

            ///Re Index all the documents
            var resumes = (from P in _Context.Resumes.Include("JobSeeker")
                          where P.JobSeeker.IsActive
                               && P.JobSeeker.HasProfileCompleted
                              && P.IsDefault
                          select P).ToList();
            result.FoundItems = resumes.Count();

            StringBuilder dbDocument = new StringBuilder();
            foreach (Resume resume in resumes)
            {
                dbDocument.Length = 0;
                //Prepare Lucene Document                
                Document luceneDocument = null;
                try
                {
                    luceneDocument = BuildLuceneDocument(resume, ref dbDocument);
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                    continue;
                }
                ///Already deleted all Lucene index documents, so no need to delete them again
                ///Add new Document to Lucene Index                
                _IndexWriter.AddDocument(luceneDocument);
                
                //Update Index Document of DB with latest changes
                ResumeIndex resumeIndex = _Context.ResumeIndexes.FirstOrDefault(P => P.ResumeID == resume.ID);
                if (resumeIndex == null)
                {
                    resumeIndex = new ResumeIndex();
                    _Context.ResumeIndexes.AddObject(resumeIndex);
                }
                
                SetPropertiesOfDbDocument(resumeIndex, resume, dbDocument, "ResumeIndexer.ReIndex");
                result.ReIndexedItems += 1;
                //Save new Document to DB and Update Progress
                processStatus.Completed += 1;
                _Context.SaveChanges();
                //System.Threading.Thread.Sleep(5000);
            }
            processStatus.Status = QueueStatus.DONE;
            _Context.SaveChanges();
            this.CommitChanges();
            this.Optimize();
            this.CloseWriter();
            return result;
        }

        #endregion IIndexer Members

        #region Helper Methods

        private void Rollback()
        {
            _IndexWriter.Rollback();
        }

        /// <summary>
        /// Initializes Index Writer and Configures it index documents.
        /// </summary>
        /// <param name="indexDirectory"></param>
        private void InitIndexWriter(String indexDirectory)
        {
            if (String.IsNullOrEmpty(indexDirectory))
                throw new ArgumentException("Resume Index directory cannot be null");

            if (!System.IO.Directory.Exists(indexDirectory))
                System.IO.Directory.CreateDirectory(indexDirectory);

            Lucene.Net.Store.Directory directory = Lucene.Net.Store.FSDirectory.Open(indexDirectory);
            _IndexWriter = new IndexWriter(directory,
                    new WhitespaceAnalyzer(),
                    //new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_30), 
                    IndexWriter.MaxFieldLength.UNLIMITED);

            _Context = new JbEntities();
        }

        /// <summary>
        /// Add a Resume Object to the Lucene Index
        /// </summary>
        /// <param name="resume"></param>
        /// <param name="appName"></param>
        private void IndexDocument(Resume resume, string appName)
        {
            StringBuilder dbDocument = new StringBuilder();
            Document doc = BuildLuceneDocument(resume, ref dbDocument);
            _IndexWriter.AddDocument(doc);
            ResumeIndex resumeIndex = _Context.ResumeIndexes.FirstOrDefault(P => P.ResumeID == resume.ID);
            if (resumeIndex == null)
            {
                resumeIndex = new ResumeIndex();
                _Context.ResumeIndexes.AddObject(resumeIndex);
                //_IndexWriter.Commit();
                //_IndexWriter.Close();
            }
            SetPropertiesOfDbDocument(resumeIndex, resume, dbDocument, appName);
            _Context.SaveChanges();
        }

        /// <summary>
        /// Deletes the existing index trace from Lucene and DB then Inserts index in Lucene and DB as well.
        /// </summary>
        /// <param name="resume"></param>
        /// <param name="appName"></param>
        private void UpdateIndex(Resume resume, string appName)
        {
            DeleteIndex(resume.ID);
            IndexDocument(resume, appName);
        }

        /// <summary>
        /// Builds a Lucene document for indexing 
        /// along with, creates a similar document to save in the database as well.
        /// </summary>
        /// <param name="resume">The Product object to be indexed</param>
        /// <param name="sb">The reference of the DB document. Which will be updated with new document properties</param>
        /// <returns>Returns a Lucene Document</returns>
        private Document BuildLuceneDocument(Resume resume, ref StringBuilder sb)
        {
            Document doc = new Document();            
            doc.Add(new Field("id", resume.ID.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));
            doc.Add(new Field("jobseeker_id", resume.JobSeekerID.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));

            ///Read Contents of the document
            String documentPath = System.IO.Path.Combine(SearchConstants.ResumeDirectory, resume.JobSeekerID.ToString(), resume.FileName);
            if (System.IO.File.Exists(documentPath))
            {
                ITextExtractor textExtractor = TextExtractorFactory.GetExtractor(documentPath);
                String extractedText = textExtractor.ExtractText();
                if(extractedText.IsNullOrEmpty())
                    extractedText = String.Empty;

                doc.Add(new Field("full_content", String.Format("{0} {1} {2}", resume.JobSeeker.Name.ToUpper(), resume.Title.ToUpper(), extractedText.ToUpper())
                    , Field.Store.NO, Field.Index.ANALYZED));

                sb.AppendFormat("full_content: {0} {1} {2} {3}", resume.JobSeeker.Name.ToUpper(), resume.Title.ToUpper(),  extractedText.ToUpper(), Environment.NewLine);

                ///Set the Resume Snapshot automatically, if it is not set by the user
                if (resume.SnapShot.IsNullOrEmpty() && !extractedText.IsNullOrEmpty())
                {                    
                    JbEntities context = new JbEntities();
                    Resume changedResume = context.Resumes.FirstOrDefault(P => P.ID == resume.ID);
                    changedResume.SnapShot = resume.SnapShot = extractedText.Length > 500 ? extractedText.Substring(0, 500).Trim() : extractedText.Trim();
                    context.SaveChanges();
                }
            }
            return doc;
        }

        /// <summary>
        /// Sets properties of the resumeIndex object to save in the databasse
        /// </summary>
        /// <param name="resumeIndex"></param>
        /// <param name="resume"></param>
        /// <param name="dbDocument"></param>
        /// <param name="appName"></param>
        private void SetPropertiesOfDbDocument(ResumeIndex resumeIndex, Resume resume, StringBuilder dbDocument, String appName)
        {
            resumeIndex.ResumeID = resume.ID;
            resumeIndex.IndexedContent = dbDocument.ToString();
            resumeIndex.ModifiedOn = DateTime.Now;
            resumeIndex.HasInLucene = true;
            resumeIndex.AppName = appName;
        }
        #endregion
    }
}
